package com.appistry.maven.plugin.far;

import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.name;
import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
import static org.twdata.maven.mojoexecutor.MojoExecutor.version;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.PluginManager;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;

import com.appistry.maven.plugin.far.remote.ssh.Command;
import com.appistry.maven.plugin.far.remote.ssh.CommandObjectFactory;
import com.appistry.maven.plugin.far.remote.ssh.Sftp;
import com.appistry.maven.plugin.far.remote.ssh.Ssh;
import com.appistry.maven.plugin.far.remote.ssh.SshConfig;
import com.appistry.maven.plugin.far.remote.ssh.SshUserInfo;
import com.appistry.maven.plugin.far.util.FileUtils;
import java.io.FileOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

public abstract class AbstractFarMojo extends AbstractMojo {


	public enum FAR_DESCRIPTOR_TYPE {
            app, far
	}

	// the compressed assembly name will begin with the artifactId to which the
	// ASSEMBLY_ID_FILENAME_SUFFIX will be appended
	private static final String ASSEMBLY_ID_FILENAME_SUFFIX = "assembly";
	private static final String ASSEMBLY_XML_PREFIX = "<assembly><id>"
			+ ASSEMBLY_ID_FILENAME_SUFFIX
			+ "</id><formats><format>zip</format></formats><includeBaseDirectory>false</includeBaseDirectory><fileSets><fileSet>";
	private static final String ASSEMBLY_XML_SUFFIX = "</fileSet></fileSets></assembly>";

	private static final String ASSEMBLY_XML_DIRECTORY_PREFIX = "<directory>";
	private static final String ASSEMBLY_XML_DIRECTORY_SUFFIX = "</directory>";
	private static final String ASSEMBLY_XML_OUTPUT_DIRECTORY_PREFIX = "<outputDirectory>";
	private static final String ASSEMBLY_XML_OUTPUT_DIRECTORY_SUFFIX = "</outputDirectory>";

	/**
	 * The Maven project.
	 * 
	 * @parameter expression="${project}"
	 * @required
	 * @readonly
	 */
	private MavenProject project;

	/**
	 * The directory of the generated fabric app
	 * 
	 * @parameter expression="${project.build.directory}"
	 * @required
	 */
	private String outputDirectory;

	/**
	 * The build final name
	 * 
	 * @parameter expression="${project.build.finalName}"
	 *            default-value="${project.artifactId}"
	 * @required
	 */
	private String finalName;

	/**
	 * The application name. This value should match the "name" attribute of the
	 * "app" element in the fabric or far application descriptor xml
	 * 
	 * FIXME: This should be auto-discovered from the appName.xml or from the
	 * output of fabric_pkg
	 * 
	 * FIXME: If finalName is not specified in the pom, then this will be "null"
	 * 
	 * @parameter expression="${project.build.finalName}"
	 * @required
	 */
	private String appName;

	/**
	 * The application extension
	 * 
	 * FIXME: This should be auto-discovered from the appName.xml or from the
	 * output of fabric_pkg
	 * 
	 * @parameter default-value="fabric"
	 */
	private String appExtension;

	/**
	 * Name of the fabric or far file. This value should match the name of the
	 * fabric or far application descriptor file (minus the extension)
	 * 
	 * FIXME: If finalName is not specified in the pom, then this will be "null"
	 * 
	 * @parameter expression="${project.build.finalName}"
	 * @required
	 */
	private String appFilename;

	/**
	 * The directory where the fabric app is built
	 * 
	 * TODO: Should I make this readonly?
	 * 
	 * @parameter 
	 *            expression="${project.build.directory}/${project.artifactId}/work"
	 * @required
	 */
	private File fabricAppDirectory;

	/**
	 * The directory where the dependent jars are copied to under the
	 * "fabricAppDirectory"
	 * 
	 * @parameter expression
	 *            ="${project.build.directory}/${project.artifactId}/work/lib"
	 * @required
	 */
	private File fabricAppLibsDirectory;

	/**
	 * The directory includes files into the packaged fabric app
	 * 
	 * @parameter expression="${basedir}/src/main/fabric-resources"
	 * @required
	 */
	private File fabricResourcesSourceDirectory;

	/**
	 * The directory includes files into the packaged fabric app
	 * 
	 * @parameter expression="${basedir}/src/main/fabric-scripts"
	 */
	private File fabricScriptsSourceDirectory;

	/**
	 * Path to directory that contains the dependent fars. The path specified
	 * should be relative to project location
	 * 
	 * @parameter
	 */
	private String farsDirectory;

	/**
	 * The remote host (SSH) to use for "fabric_pkg"
	 * 
	 * @parameter
	 */
	private String remoteSshHost;

	/**
	 * The remote port (SSH) to connect to
	 * 
	 * @parameter
	 */
	private int remoteSshPort;

	/**
	 * The username to connect to the remote host
	 * 
	 * @parameter
	 */
	private String remoteSshUsername;

	/**
	 * The password for the username that is used to connect to the remote host
	 * 
	 * @parameter
	 */
	private String remoteSshPassword;

	/**
	 * The Maven session
	 * 
	 * @parameter expression="${session}"
	 * @required
	 * @readonly
	 */
	private MavenSession session;

	/**
	 * Base directory of the project.
	 * 
	 * @parameter default-value="${basedir}"
	 * @required
	 * @readonly
	 */
	private File basedir;

	/**
	 * The Maven PluginManager
	 * 
	 * @component
	 * @required
	 */
	private PluginManager pluginManager;

        /**
         * Use specified version instead of incrementing
         *
         * @parameter
         */
        private String farVersion;

        /**
         * Update the Far version from the cloud (if available)
         *
         * @parameter expression="false"
         */
        private boolean updateCloudFarVersion;

        /**
         * Skip incrementing the far version
         *
         * @parameter expression="false"
         */
        private boolean farVersionUpdateSkip;

	public MavenProject getProject() {
		return project;
	}

	public String getOutputDirectory() {
		return outputDirectory;
	}

	public void setOutputDirectory(String outputDirectory) {
		this.outputDirectory = outputDirectory;
	}

	public String getFinalName() {
		return finalName;
	}

	public void setFinalName(String finalName) {
		this.finalName = finalName;
	}

	public String getAppName() {
		return appName;
	}

	public void setAppName(String appName) {
		this.appName = appName;
	}

	public String getAppExtension() {
		return appExtension;
	}

	public void setAppExtension(String appExtension) {
		this.appExtension = appExtension;
	}

	public String getAppFilename() {
		return appFilename;
	}

	public void setAppFilename(String appFilename) {
		this.appFilename = appFilename;
	}

	public File getFabricAppDirectory() {
		return fabricAppDirectory;
	}

	public void setFabricAppDirectory(File fabricAppDirectory) {
		this.fabricAppDirectory = fabricAppDirectory;
	}

	public File getFabricAppLibsDirectory() {
		return fabricAppLibsDirectory;
	}

	public void setFabricAppLibsDirectory(File fabricAppLibsDirectory) {
		this.fabricAppLibsDirectory = fabricAppLibsDirectory;
	}

	public File getFabricResourcesSourceDirectory() {
		return fabricResourcesSourceDirectory;
	}

	public void setFabricResourcesSourceDirectory(
			File fabricResourcesSourceDirectory) {
		this.fabricResourcesSourceDirectory = fabricResourcesSourceDirectory;
	}

	public File getFabricScriptsSourceDirectory() {
		return fabricScriptsSourceDirectory;
	}

	public void setFabricScriptsSourceDirectory(
			File fabricScriptsSourceDirectory) {
		this.fabricScriptsSourceDirectory = fabricScriptsSourceDirectory;
	}

	public String getFarsDirectory() {
		return farsDirectory;
	}

	public void setFarsDirectory(String farsDirectory) {
		this.farsDirectory = farsDirectory;
	}

	public MavenSession getSession() {
		return session;
	}

	public File getBasedir() {
		return basedir;
	}

	public String getRemoteSshHost() {
		return remoteSshHost;
	}

	public void setRemoteSshHost(String remoteSshHost) {
		this.remoteSshHost = remoteSshHost;
	}

	public int getRemoteSshPort() {
		return remoteSshPort;
	}

	public void setRemoteSshPort(int remoteSshPort) {
		this.remoteSshPort = remoteSshPort;
	}

	public String getRemoteSshUsername() {
		return remoteSshUsername;
	}

	public void setRemoteSshUsername(String remoteSshUsername) {
		this.remoteSshUsername = remoteSshUsername;
	}

	public String getRemoteSshPassword() {
		return remoteSshPassword;
	}

	public void setRemoteSshPassword(String remoteSshPassword) {
		this.remoteSshPassword = remoteSshPassword;
	}

        public String getFarVersion() {
            return farVersion;
        }
        
        public void setFarVersion(String farVersion) {
            this.farVersion = farVersion;
        }

        public boolean isUpdateCloudFarVersion() {
            return updateCloudFarVersion;
        }

        public boolean isFarVersionUpdateSkip() {
            return farVersionUpdateSkip;
        }

	/**
	 * 
	 * @param fabricAppDirectory
	 * @throws MojoExecutionException
	 */
	public void buildFar(File fabricAppDirectory) throws MojoExecutionException {

		Log log = getLog();

		log.info("Root Folder:" + session.getExecutionRootDirectory());
		log.info("Current Folder:" + basedir);

		if (!fabricAppDirectory.exists()) {
			if (!fabricAppDirectory.mkdirs()) {
				throw new MojoExecutionException("Failed to create directory: "
						+ fabricAppDirectory.getAbsolutePath());
			}
		}

		buildApp(project, fabricAppDirectory);
	}

	private void buildApp(MavenProject project, File fabricAppDirectory)
			throws MojoExecutionException {
		Log log = getLog();
		log.debug("In buildApp");

		final long startTime = System.currentTimeMillis();
		log.info("Assembling FabricApp[" + getAppName() + "] in ["
				+ fabricAppDirectory + "]");

		// copy fabric resources
		copyFabricResourceFiles(getFabricResourcesSourceDirectory(),
				getFabricAppDirectory());

                // increment fabric/far version if skip is true
                if (!isFarVersionUpdateSkip()) {
                    updateAppDescriptorVersion(getFabricResourcesSourceDirectory(),
                                    getFabricAppDirectory());
                }

		// copy fabric scripts
		if (getFabricScriptsSourceDirectory().exists()) {
			copyFabricResourceFiles(getFabricScriptsSourceDirectory(),
					getFabricAppDirectory());
		}

		// copy primary artifact
		copyPrimaryArtifact();

		// copy application dependencies
		assembleFabricDependencies(getFabricAppLibsDirectory());

		// package app
		packageFabricApp();

		log.info("FabricApp assembled in["
				+ (System.currentTimeMillis() - startTime) + " msecs]");
	}

	protected void copyFabricResourceFiles(File fabricResourcesSourceDirectory,
			File fabricAppDirectory) throws MojoExecutionException {
		Log log = getLog();
		log.info("Copying fabric resources from '"
				+ fabricResourcesSourceDirectory.getAbsolutePath() + "' to '"
				+ fabricAppDirectory + "'");

		try {
			FileUtils.copyDirectory(fabricResourcesSourceDirectory,
					fabricAppDirectory);
		} catch (IOException ioe) {
			final String errorMessage = "Failed to copy fabric resources.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ ioe.getLocalizedMessage(), ioe);
		}
	}

	protected void assembleFabricDependencies(
			File fabricApplicationLibsDirectory) throws MojoExecutionException {
		Log log = getLog();
		log.debug("In copyDependencies");

		if (!fabricApplicationLibsDirectory.exists()) {
			if (!fabricApplicationLibsDirectory.mkdirs()) {
				throw new MojoExecutionException(
						"Failed to created directory: "
								+ fabricApplicationLibsDirectory
										.getAbsolutePath());
			}
		}

		try {
			// include runtime dependencies
			executeMojo(plugin(groupId("org.apache.maven.plugins"),
					artifactId("maven-dependency-plugin"), version("2.0")),
					goal("copy-dependencies"), configuration(element(
							name("outputDirectory"),
							fabricApplicationLibsDirectory.getAbsolutePath()),
							element(name("stripVersion"), "true"), element(
									name("overWriteIfNewer"), "true"), element(
									name("includeScope"), "runtime")),
					executionEnvironment(project, session, pluginManager));

			// include system dependencies
			executeMojo(plugin(groupId("org.apache.maven.plugins"),
					artifactId("maven-dependency-plugin"), version("2.0")),
					goal("copy-dependencies"), configuration(element(
							name("outputDirectory"),
							fabricApplicationLibsDirectory.getAbsolutePath()),
							element(name("stripVersion"), "true"), element(
									name("overWriteIfNewer"), "true"), element(
									name("includeScope"), "system")),
					executionEnvironment(project, session, pluginManager));
		} catch (MojoExecutionException mee) {
			final String errorMessage = "Failed to copy dependencies.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ mee.getLocalizedMessage(), mee);
		}
	}

	protected void updateAppDescriptorVersion(File fabricResourcesSourceDirectory,
			File fabricAppDirectory) throws MojoExecutionException {
		Log log = getLog();

		try {
                    File appFile = new File(fabricAppDirectory, getAppFilename() + ".xml");
                    log.info(appFile.getAbsolutePath() + " exists : " + appFile.exists());
                    if (!appFile.exists()) {
                        FileUtils.copyFile(new File(getFabricResourcesSourceDirectory(), getAppFilename() + ".xml"), new File(getFabricAppDirectory(), getAppFilename() + ".xml"));
                    }

                    log.info("Updating version number in file: " + getFabricAppDirectory().getPath() + File.separator + getAppFilename() + ".xml");

                    updateFarVersion(appFile);

		} catch (IOException ioe) {
			final String errorMessage = "Failed to copy fabric resources.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ ioe.getLocalizedMessage(), ioe);
		}
	}


	protected void copyPrimaryArtifact() throws MojoExecutionException {
		Log log = getLog();

		// BugFix: For service/package FARs, the packaging element could be
		// "pom" or "far", therefore there is no primary artifact and therefore
		// nothing to copy. Hence return-fast.
		if ("pom".equalsIgnoreCase(getProject().getPackaging())
				|| "far".equalsIgnoreCase(getProject().getPackaging())) {
			return;
		}

		Artifact artifact = getProject().getArtifact();

		File primaryArtifact = artifact.getFile();
		try {
			if (primaryArtifact == null || !primaryArtifact.exists()) {
				final String errorMessage = "Failed to copy primary artifact. Did you run: mvn clean package";
				log.error(errorMessage);
				throw new MojoExecutionException(errorMessage);
			}

			// TODO: if "stripVersionOfDependencies" configuration parameter is
			// set to true then use destination (for copyFile) as
			// "artifact.getArtifactId() + artifact.getType()" instead of
			// "primaryArtifact.getName()"
			log
					.info("Copying primary artifact '"
							+ primaryArtifact.getAbsolutePath() + "' to '"
							+ getFabricAppLibsDirectory() + File.separator
							+ artifact.getArtifactId() + "."
							+ artifact.getType() + "'");

			FileUtils.copyFile(primaryArtifact, new File(
					getFabricAppLibsDirectory(), artifact.getArtifactId() + "."
							+ artifact.getType()));
		} catch (IllegalArgumentException iae) {
			final String errorMessage = "Failed to copy primary artifact.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ iae.getLocalizedMessage(), iae);
		} catch (IOException ioe) {
			final String errorMessage = "Failed to copy primary artifact.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ ioe.getLocalizedMessage(), ioe);
		}
	}

	protected void packageFabricApp() throws MojoExecutionException {
		Log log = getLog();
		log.debug("In packageFabricApp");
		
		if (getRemoteSshHost() != null
				&& getRemoteSshHost().trim().length() > 0) {
			log.info("Remote Host: " + getRemoteSshHost());
			
			createCompressedAssembly();

			transferFileToRemoteHost();

			performFabricPackageOnRemoteHost();

			getTargetArtifactFromRemoteHost();

			// TODO: Should I cleanup the assembly.xml that is generated in the
			// "target" directory?
			cleanupRemoteHost();

		} else {
			performFabricPackage();
		}

	}

	protected void cleanupRemoteHost() throws MojoExecutionException {
		Log log = getLog();

		// erase commands - a tad bit dangerous, isn't it?
		final String eraseCompressedFile = "rm -f " + getAssemblyFilename()
				+ getAssemblyFilenameExtension();
		final String eraseUnCompressedDir = "rm -rf " + getAppName();

		Ssh ssh = new Ssh();
		ssh.setSshConfig(getSshConfig());
		ssh.setCommand(eraseCompressedFile + "; " + eraseUnCompressedDir);

		try {
			// Command c = new SshCommand(ssh);
			Command c = CommandObjectFactory.getInstance(ssh);
			c.execute();
		} catch (Exception e) {
			final String errorMessage = "Failed to fabric package to remote host "
					+ getRemoteSshHost();
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ e.getLocalizedMessage(), e);
		}

	}

	protected void getTargetArtifactFromRemoteHost()
			throws MojoExecutionException {
		Log log = getLog();

		Sftp sftp = new Sftp();
		sftp.setSshConfig(getSshConfig());

		log.info("About to get target artifact: " + getAppName()
				+ File.separator + getAppName() + "." + getAppExtension());

		// FIXME: This will fail when run from a windows machine that SSHes
		// stuff over to a Linux machine since the "File.separator" used will be
		// "\" (Windows) instead of "/" (Unix).
		// I think the fix could be to discover the target OS and use the
		// correct "File.separator". Running the "uname" command will return
		// CYGWIN_NT-5.1 if cygwin_ssh is used on a Windows machine.
		// For now, do not use a remoteSshHost when running from Windows. Make
		// sure that CloudIQ is installed on the machine so that this plugin
		// does the fabric package locally.
		sftp.setSrcFilename(getAppName() + File.separator + getAppName() + "."
				+ getAppExtension());
		sftp.setDestFilename(getOutputDirectory() + File.separator
				+ getAppName() + "." + getAppExtension());
		sftp.setTransferType(Sftp.TYPE.get);

		try {
			// Command c = new SftpCommand(sftp);
			Command c = CommandObjectFactory.getInstance(sftp);
			c.execute();
		} catch (Exception e) {
			final String errorMessage = "Failed to get target artifact from remote host "
					+ getRemoteSshHost();
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ e.getLocalizedMessage(), e);
		}

	}

	protected void transferFileToRemoteHost() throws MojoExecutionException {
		Log log = getLog();

		log.info("About to transfer assembly " + getOutputDirectory()
				+ File.separator + getAssemblyFilename()
				+ getAssemblyFilenameExtension() + " to remote host");

		Sftp sftp = new Sftp();
		sftp.setSshConfig(getSshConfig());
		sftp.setSrcFilename(getOutputDirectory() + File.separator
				+ getAssemblyFilename() + getAssemblyFilenameExtension());
		sftp.setDestFilename(getAssemblyFilename()
				+ getAssemblyFilenameExtension());
		sftp.setTransferType(Sftp.TYPE.put);

		try {
			// Command c = new SftpCommand(sftp);
			Command c = CommandObjectFactory.getInstance(sftp);
			c.execute();
		} catch (Exception e) {
			final String errorMessage = "Failed to transfer assembly to remote host "
					+ getRemoteSshHost();
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ e.getLocalizedMessage(), e);
		}
	}

	protected void performFabricPackageOnRemoteHost()
			throws MojoExecutionException {
		Log log = getLog();

		Ssh ssh = new Ssh();
		ssh.setSshConfig(getSshConfig());
		ssh.setCommand(concantenateCommands(getCommands()));

		try {
			// Command c = new SshCommand(ssh);
			Command c = CommandObjectFactory.getInstance(ssh);
			c.execute();
		} catch (Exception e) {
			final String errorMessage = "Failed to fabric package on remote host "
					+ getRemoteSshHost();
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ e.getLocalizedMessage(), e);
		}
	}

	private SshConfig getSshConfig() {
		return new SshConfig(getRemoteSshHost(), getRemoteSshPort(),
				new SshUserInfo(getRemoteSshUsername(), getRemoteSshPassword(),
						true));
	}

	protected void performFabricPackage() throws MojoExecutionException {
		Log log = getLog();
		log.debug("In performFabricPackage");

		try {
			// FIXME: Local fabric_pkg puts the file in the ${basedir}. Remote
			// fabric_pkg puts the file in the ${project.build.outputDirectory}.
			// Need to standardize.
			// Fix might be to "get" remote target artifact in the ${basedir}. I
			// would prefer it to go in the ${project.build.outputDirectory}
			// since it is a "generated" artifact. Need to do the RIGHT thing if
			// I have time

			// exec fabric_pkg
			executeMojo(plugin(groupId("org.codehaus.mojo"),
					artifactId("exec-maven-plugin"), version("1.1.1")),
					goal("exec"), configuration(element(name("executable"),
							"fabric_pkg"), element("arguments", element(
							"argument", "create"), element("argument",
							getFabricAppDirectory() + File.separator
									+ getAppFilename() + ".xml"))),
					executionEnvironment(project, session, pluginManager));
		} catch (MojoExecutionException mee) {
			final String errorMessage = "Failed to package application.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ mee.getLocalizedMessage(), mee);
		}

	}

	protected void createCompressedAssembly() throws MojoExecutionException {
		Log log = getLog();

		writeAssemblyFile(new File(getOutputDirectory(), "assembly.xml"));

		File descriptor = new File(getOutputDirectory(), "assembly.xml");
		try {
			executeMojo(
					plugin(groupId("org.apache.maven.plugins"),
							artifactId("maven-assembly-plugin"),
							version("2.2-beta-2")), goal("single"),
					configuration(element(name("descriptors"), element(
							"descriptor", descriptor.getAbsolutePath()))),
					executionEnvironment(project, session, pluginManager));
			log.info("Finished zipping");
		} catch (MojoExecutionException mee) {
			final String errorMessage = "Failed to zip work directory.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ mee.getLocalizedMessage(), mee);
		}
	}

	protected void writeAssemblyFile(File file) throws MojoExecutionException {
		Log log = getLog();

		String assemblyDirectory = ASSEMBLY_XML_DIRECTORY_PREFIX
				+ getFabricAppDirectory().getAbsolutePath()
				+ ASSEMBLY_XML_DIRECTORY_SUFFIX;
		String assemblyOutputDirectory = ASSEMBLY_XML_OUTPUT_DIRECTORY_PREFIX
				+ getAppName() + ASSEMBLY_XML_OUTPUT_DIRECTORY_SUFFIX;

		BufferedReader br = null;
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(file));

			br = new BufferedReader(new StringReader(ASSEMBLY_XML_PREFIX
					+ assemblyDirectory + assemblyOutputDirectory
					+ ASSEMBLY_XML_SUFFIX));
			String line;
			while ((line = br.readLine()) != null) {
				bw.write(line);
			}
		} catch (IOException ioe) {
			final String errorMessage = "Failed to generate assembly file.";
			log.error(errorMessage);
			throw new MojoExecutionException(errorMessage + " Details: "
					+ ioe.getLocalizedMessage(), ioe);
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException ioe) {
					log.warn("Could not close writer: "
							+ ioe.getLocalizedMessage());
				}
			}
			if (br != null) {
				try {
					br.close();
				} catch (IOException ioe) {
					log.warn("Could not close reader: "
							+ ioe.getLocalizedMessage());
				}
			}
		}

	}

	private String[] getCommands() {
                Log log = getLog();
		final String sourceFabricEnv = "source /etc/fabric_env";
		final String unZipAssembly = "unzip -o " + getAssemblyFilename()
				+ getAssemblyFilenameExtension();
		final String changeDirectory = "cd " + getAppName();

		final String fabricPackage;
		if (getFarsDirectory() != null
				&& getFarsDirectory().trim().length() > 0) {
			fabricPackage = "fabric_pkg " + "-fars-dir=" + getFarsDirectory()
					+ " " + getAppFilename() + ".xml";
		} else {
			fabricPackage = "fabric_pkg " + getAppFilename() + ".xml";
		}

		List<String> commands = new ArrayList<String>();
		// FIXME: if remote host is CYGWIN_NT then there will not be a
		// fabric_env file (env variables are set differently on Windows).
		// Therefore the first command "source /etc/fabric_env" will fail.
		// Running "uname" using SshCommand before adding these commands will
		// help determine whether the remote host is Windows or Linux
		commands.add(sourceFabricEnv);
		commands.add(unZipAssembly);
		commands.add(changeDirectory);
		commands.add(fabricPackage);

		return (String[]) commands.toArray(new String[commands.size()]);
	}

	private String concantenateCommands(String[] commands) {
		StringBuilder commandToSend = new StringBuilder();
		for (String command : commands) {
			commandToSend.append(command).append(";");
		}

		return commandToSend.toString();
	}

	private String getAssemblyFilename() {
		String assemblyFileName = null;

		if (getFinalName() != null && getFinalName().trim().length() > 0) {
			assemblyFileName = getFinalName() + "-"
					+ ASSEMBLY_ID_FILENAME_SUFFIX;
		} else {
			assemblyFileName = getProject().getArtifactId() + "-"
					+ getProject().getArtifact().getVersion() + "-"
					+ ASSEMBLY_ID_FILENAME_SUFFIX;
		}

		return assemblyFileName;
	}

	private String getAssemblyFilenameExtension() {
		return ".zip";
	}

	protected boolean existsFabricResourcesSourceDirectory()
			throws MojoExecutionException {
		Log log = getLog();
		if (!getFabricResourcesSourceDirectory().exists()) {
			// TODO: Is returning false (ignore creating fabric/far even if
			// explicitly stated in the command line) sufficient?
			// Initially I was thinking of throwing an exception
			// (fabricResourcesSrc dir does not exist), but that would fail a
			// multi-module build in which one of the modules builds the fabric.
			// Therefore only returning "false" for now.
			return false;
		}

		return true;
	}

    protected void updateFarVersion(final File appFile) throws MojoExecutionException {
        Log log = getLog();

        log.info("In updateFarVersion : " + appFile.exists());

        // skip goal if farIncrementSkip == true
        if (isFarVersionUpdateSkip()) {
            return;
        }

        // if farVersion is specified then use it rather than incrementing
        String currentFarVersion = getFarVersion();
        if (StringUtils.isBlank(currentFarVersion)) {
            // check if updateCloudFarVersion == true
            if (isUpdateCloudFarVersion()) {
                // TODO: get version from cloud
                // currentFarVersion = ....
                // TODO: Once the code is added, update the parameter expression to "true"
            }
        }
        log.info("currentFarVersion: " + currentFarVersion);

        log.info("Updating far version...");

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.parse(appFile);

            Node child = doc.getFirstChild();
            log.debug("First child: " + child.getNodeName());
            String nodeName = child.getNodeName();
            if (FAR_DESCRIPTOR_TYPE.app.name().equalsIgnoreCase(nodeName) || FAR_DESCRIPTOR_TYPE.far.name().equalsIgnoreCase(nodeName)) {
                NamedNodeMap childAttrs = child.getAttributes();
//                log.debug("childAttrs.size: " + childAttrs.getLength());

                // if updateCloudFarVersion == false, then get the version from the far.xml itself
                if (StringUtils.isBlank(currentFarVersion)) {
                    currentFarVersion = childAttrs.getNamedItem("version").getNodeValue();
                }

                // set the new version
                childAttrs.getNamedItem("version").setNodeValue(getNewVersion(currentFarVersion));
            }

            // write the file
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//            StreamResult result = new StreamResult(new StringWriter());
            DOMSource source = new DOMSource(doc);
            transformer.transform(source, new StreamResult(new FileOutputStream(appFile)));

            log.info("Finished updating the version");
            
        } catch (Exception e) {
            final String errorMessage = "Failed to update the version for file " + getAppFilename() + ".xml";
            log.error(errorMessage);
            throw new MojoExecutionException(errorMessage + ". Details: " + e.getLocalizedMessage(), e);

        } 

    }

    protected String getNewVersion(final String currentFarVersion) {
        Log log = getLog();
        
        // if getFarVersion is not blank then return it
        if (!StringUtils.isBlank(getFarVersion())) {
            log.info("farVersion specified: " + currentFarVersion);
            return currentFarVersion;
        }

        // this is the first time the fabric / far is being deployed
        // and the user did not specify a version
        if (StringUtils.isBlank(currentFarVersion)) {
            log.info("farVersion not specified; no cloud version found either. Setting new version to 1");
            return "1";
        }

        int version = 0;
        try {
            version = Integer.parseInt(currentFarVersion);
            // increment the version
            log.info("farVersion not specified, incrementing version (" + currentFarVersion + ") by 1");
        } catch(NumberFormatException nfe) {
            final String errorMessage = "Could not parse " + currentFarVersion + " as an int.";
            log.error(errorMessage);
            throw new NumberFormatException(errorMessage + " " + nfe.getLocalizedMessage());
        }

        return String.valueOf(version + 1);
    }

}
